EAP-TLS_Solution/EAP-TLS Client/lib/web_api_client.c (398 lines of code) (raw):

#include <errno.h> #include <stdbool.h> #include <string.h> #include <stdlib.h> #include <signal.h> #include <curl/curl.h> #include "../applibs_versions.h" #include <applibs/log.h> #include <applibs/networking.h> #include <applibs/wificonfig.h> #include <applibs/storage.h> #include <applibs/certstore.h> #include <tlsutils/deviceauth_curl.h> #include "../parson.h" #include "_environment_config.h" #include "web_api_client.h" /// <summary> /// Boolean string constants for generating JSON documents /// </summary> const char *const strTrue = "true"; const char *const strFalse = "false"; /// <summary> /// JSON tokens returned by the WebAPI response, used for parsing. /// Note: these definitions must match the ones returned by the WebAPI's JSON response! /// </summary> #if defined(WEBAPI_SERVER) // Add JSON property names, specific to the WebApi's JSON request & response schema. #endif #if defined(WEBAPI_KESTREL) const char *const webApiResponse_Timestamp = "timestamp"; const char *const webApiResponse_RootCACertificate = "rootCACertificate"; const char *const webApiResponse_EapTlsNetworkSsid = "eapTlsNetworkSsid"; const char *const webApiResponse_ClientIdentity = "clientIdentity"; const char *const webApiResponse_ClientPublicCertificate = "clientPublicCertificate"; const char *const webApiResponse_ClientPrivateKey = "clientPrivateKey"; const char *const webApiResponse_ClientPrivateKeyPass = "clientPrivateKeyPass"; /// <summary> /// WebAPI GET query fields /// Note: These definitions must match the ones used in the WebAPI's method signature!!! /// </summary> const char *const webApi_RootCertificateField = "needRootCACertificate"; const char *const webApi_ClientCertificateField = "needClientCertificate"; #endif ////////////////////////////////////////////////////////////// // Logging utilities ////////////////////////////////////////////////////////////// static void LogCurlError(const char *message, CURLcode curlErrCode) { Log_Debug("%s\n(curl err=%d, '%s')\n", message, curlErrCode, curl_easy_strerror(curlErrCode)); } ////////////////////////////////////////////////////////////// // WebAPI related functions ////////////////////////////////////////////////////////////// EapTlsResult EapTls_ParseMdmWebApiResponse(MemoryBlock *responseBlock, WebApiResponse *outResponse) { EapTlsResult iRes = EapTlsResult_Error; if (NULL != outResponse && NULL != responseBlock && NULL != responseBlock->data) { memset(outResponse, 0, sizeof(WebApiResponse)); size_t nullTerminatedJsonSize = responseBlock->size + 1; char *nullTerminatedJsonString = (char *)malloc(nullTerminatedJsonSize); if (nullTerminatedJsonString == NULL) { Log_Debug("ERROR: could not allocate buffer for parsing the WebAPI response.\n"); } else { // Copy the provided buffer to a null terminated buffer. memcpy(nullTerminatedJsonString, responseBlock->data, responseBlock->size); nullTerminatedJsonString[nullTerminatedJsonSize - 1] = 0; // Add the null terminator at the end. // Parse the response JSON_Value *rootProperties = json_parse_string(nullTerminatedJsonString); if (rootProperties == NULL) { Log_Debug("WARNING: Cannot parse the response as JSON content.\n"); } else { JSON_Object *rootObject = json_value_get_object(rootProperties); #if defined(WEBAPI_SERVER) // TBD, depending on the WebApi's JSON response schema #endif #if defined(WEBAPI_KESTREL) const char *s = json_object_get_string(rootObject, webApiResponse_Timestamp); if (NULL != s) { strncpy(outResponse->timestamp, s, sizeof(outResponse->timestamp) - 1); s = json_object_get_string(rootObject, webApiResponse_RootCACertificate); if (NULL != s) { strncpy(outResponse->rootCACertficate, s, sizeof(outResponse->rootCACertficate) - 1); s = json_object_get_string(rootObject, webApiResponse_EapTlsNetworkSsid); if (NULL != s) { strncpy(outResponse->eapTlsNetworkSsid, s, sizeof(outResponse->eapTlsNetworkSsid) - 1); s = json_object_get_string(rootObject, webApiResponse_ClientIdentity); if (NULL != s) { strncpy(outResponse->clientIdentity, s, sizeof(outResponse->clientIdentity) - 1); s = json_object_get_string(rootObject, webApiResponse_ClientPublicCertificate); if (NULL != s) { strncpy(outResponse->clientPublicCertificate, s, sizeof(outResponse->clientPublicCertificate) - 1); s = json_object_get_string(rootObject, webApiResponse_ClientPrivateKey); if (NULL != s) { strncpy(outResponse->clientPrivateKey, s, sizeof(outResponse->clientPrivateKey) - 1); s = json_object_get_string(rootObject, webApiResponse_ClientPrivateKeyPass); if (NULL != s) { strncpy(outResponse->clientPrivateKeyPass, s, sizeof(outResponse->clientPrivateKeyPass) - 1); iRes = EapTlsResult_Success; } } } } } } } #endif } if (EapTlsResult_Success != iRes) { Log_Debug("ERROR parsing response.\n"); } // Release the allocated memory. json_value_free(rootProperties); } // Release the allocated memory. free(nullTerminatedJsonString); } else { Log_Debug("ERROR, bad parameters.\n"); } return iRes; } static size_t EapTls_StoreDownloadedDataCallback(void *chunks, size_t chunkSize, size_t chunksCount, void *memBlock) { MemoryBlock *block = (MemoryBlock *)memBlock; size_t additionalDataSize = chunkSize * chunksCount; block->data = realloc(block->data, block->size + additionalDataSize + 1); if (NULL == block->data) { Log_Debug("Out of memory, realloc returned NULL: errno=%d (%s)'n", errno, strerror(errno)); //abort(); return 0; } else { memcpy(block->data + block->size, chunks, additionalDataSize); block->size += additionalDataSize; block->data[block->size] = 0; // Ensure the block of memory is null terminated. } return additionalDataSize; } static CURLcode EapTls_DeviceAuth_CurlSslFunc(CURL *curl, void *sslctx, void *userCtx) { DeviceAuthSslResult err = DeviceAuth_SslCtxFunc(sslctx); switch (err) { case DeviceAuthSslResult_Success: Log_Debug("DeviceAuthSslResult_Success (%d)\n", err); break; case DeviceAuthSslResult_GetTenantIdError: Log_Debug("DeviceAuthSslResult_GetTenantIdError (%d - '%s')\n", err, "Failed to access the current application's tenant id"); break; case DeviceAuthSslResult_GetTenantCertificateError: Log_Debug("DeviceAuthSslResult_GetTenantCertificateError (%d - '%s')\n", err, "Failed to load the device authentication certificate for the tenant"); break; case DeviceAuthSslResult_EnableHwSignError: Log_Debug("DeviceAuthSslResult_EnableHwSignError (%d - '%s')\n", err, "Failed to enable hardware signing"); break; } return CURLE_OK; } EapTlsResult EapTls_CallWebApi(const char *url, const char *queryString, const char *putString, const char *webApiRootCACertRelativePath, MemoryBlock *responseBlock) { EapTlsResult iRes = EapTlsResult_Error; if (NULL != url && NULL != webApiRootCACertRelativePath && NULL != responseBlock) { // https://curl.haxx.se/libcurl/c/https.html curl_global_init(CURL_GLOBAL_ALL); CURL *curlHandle = curl_easy_init(); if (curlHandle) { CURLcode res; // Set up for DAA mutual authentication // Device: https://learn.microsoft.com/en-us/azure-sphere/app-development/curl // WebAPI: https://learn.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth#special-considerations-for-certificate-validation // Set up the WebAPI's URL with the proper GET query char call_url[MAX_URL_LEN + 1]; memset(call_url, 0, sizeof(call_url)); if (NULL != queryString) { snprintf(call_url, sizeof(call_url), "%s?%s", url, queryString); } else { strncpy(call_url, url, sizeof(call_url) - 1); } // Activate verbose logging if ((res = curl_easy_setopt(curlHandle, CURLOPT_VERBOSE, 1L)) != CURLE_OK) { LogCurlError("FAILED curl_easy_setopt CURLOPT_VERBOSE", res); } if ((res = curl_easy_setopt(curlHandle, CURLOPT_URL, call_url)) != CURLE_OK) { LogCurlError(" FAILED curl_easy_setopt CURLOPT_URL", res); } else { // Set up the PUT fields, if any if (NULL != putString) { if ((res = curl_easy_setopt(curlHandle, CURLOPT_POST, 1L)) != CURLE_OK) { LogCurlError(" FAILED curl_easy_setopt CURLOPT_POST", res); } else { struct curl_slist *hs = NULL; hs = curl_slist_append(hs, "Content-Type: application/json"); if ((res = curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, hs)) != CURLE_OK) { LogCurlError(" FAILED curl_easy_setopt CURLOPT_HTTPHEADER", res); } else { char hdrTemp[MAX_URL_LEN + 1]; snprintf(hdrTemp, sizeof(hdrTemp), "Content-Length: %zd", strlen(putString)); hs = curl_slist_append(hs, hdrTemp); if ((res = curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, hs)) != CURLE_OK) { LogCurlError(" FAILED curl_easy_setopt CURLOPT_HTTPHEADER", res); } else { if ((res = curl_easy_setopt(curlHandle, CURLOPT_POSTFIELDS, putString)) != CURLE_OK) { LogCurlError(" FAILED curl_easy_setopt CURLOPT_POSTFIELDS", res); } } } } } //if ((res = curl_easy_setopt(curlHandle, CURLOPT_SSL_VERIFYHOST, 0)) != CURLE_OK) //{ // LogCurlError("curl_easy_setopt CURLOPT_SSL_VERIFYHOST", res); //} if ((res = curl_easy_setopt(curlHandle, CURLOPT_SSL_VERIFYPEER, 0)) != CURLE_OK) { LogCurlError("FAILED curl_easy_setopt CURLOPT_SSL_VERIFYPEER", res); } else { // The simplest way to perform device authentication is to configure DeviceAuth_CurlSslFunc as the callback function for curl SSL authentication if ((res = curl_easy_setopt(curlHandle, CURLOPT_SSL_CTX_FUNCTION, EapTls_DeviceAuth_CurlSslFunc)) != CURLE_OK) { LogCurlError("curl_easy_setopt CURLOPT_SSL_CTX_FUNCTION", res); } else { // libcurl on Azure Sphere supports TLS 1.2 and has deprecated TLS 1.0 and TLS 1.1 in alignment with the broader Microsoft TLS security strategy if ((res = curl_easy_setopt(curlHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2)) != CURLE_OK) { LogCurlError("FAILED curl_easy_setopt CURLOPT_SSLVERSION", res); } else { // Get the full path to the certificate file used to authenticate the WebAPI's server identity // #NOTE: currently APIs do not support passing libcurl a certificate Id char *certificatePath = Storage_GetAbsolutePathInImagePackage(webApiRootCACertRelativePath); if (certificatePath == NULL) { Log_Debug("The certificate path could not be resolved: errno=%d (%s)\n", errno, strerror(errno)); } else { // Set the path for the certificate file that cURL uses to validate the server certificate if ((res = curl_easy_setopt(curlHandle, CURLOPT_CAINFO, certificatePath)) != CURLE_OK) { LogCurlError("curl_easy_setopt CURLOPT_CAINFO", res); } else { // Let cURL follow any HTTP 3xx redirects. Important: any redirection to different domain names // requires that domain name to be added to app_manifest.json if ((res = curl_easy_setopt(curlHandle, CURLOPT_FOLLOWLOCATION, 1L)) != CURLE_OK) { LogCurlError("FAILED curl_easy_setopt CURLOPT_FOLLOWLOCATION", res); } else { // Specify a user agent if ((res = curl_easy_setopt(curlHandle, CURLOPT_USERAGENT, "libcurl-agent/1.0")) != CURLE_OK) { LogCurlError("FAILED curl_easy_setopt CURLOPT_USERAGENT", res); } else { // Set the custom parameter of the callback to the memory block if ((res = curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, (void *)responseBlock)) != CURLE_OK) { LogCurlError("FAILED curl_easy_setopt CURLOPT_WRITEDATA", res); } else { // Set up callback for cURL to use when downloading data if ((res = curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, EapTls_StoreDownloadedDataCallback)) != CURLE_OK) { LogCurlError("FAILED curl_easy_setopt CURLOPT_FOLLOWLOCATION", res); } else { Log_Debug("Connecting to %s...\n", call_url); if ((res = curl_easy_perform(curlHandle)) == CURLE_OK) { // #NOTE: SSL renegotiation is not currently supported by Azure Sphere Log_Debug("\n -===- Downloaded content (%zu bytes): -===-\n", responseBlock->size); Log_Debug("%s\n", responseBlock->data); iRes = EapTlsResult_Success; } else { LogCurlError("FAILED curl_easy_perform", res); iRes = EapTlsResult_FailedConnectingToMdmWebApi; } } } } } } } } } } } curl_easy_cleanup(curlHandle); } else { Log_Debug("FAILED initializing cURL!"); iRes = EapTlsResult_Error; } } else { Log_Debug("ERROR: bad parameters!"); iRes = EapTlsResult_BadParameters; } return iRes; } EapTlsResult EapTls_CallMdmWebApi(EapTlsConfig *eapTlsConfig, bool getRootCACertificate, bool getClientCertificate, MemoryBlock *responseBlock) { EapTlsResult iRes = EapTlsResult_Error; #if USE_TEST_WEB_API_RESPONSE // Mimic libcurl: just like in curl_easy_perform, the allocated memory has to be free-ed by the caller size_t size = strlen(test_web_api_response_json) responseBlock->data = malloc(size + 1); if (NULL != responseBlock->data) { memcpy(responseBlock->data, test_web_api_response_json, size); responseBlock->size = size; iRes = EapTlsResult_SUCCESS; } #else if (NULL != eapTlsConfig && NULL != responseBlock) { // Set up the WebAPI's URL with the proper GET query, so to ask just for the specific certificates we need char query_str[MAX_URL_LEN + 1]; memset(query_str, 0, sizeof(query_str)); if (EapTlsResult_Error == EapTls_IsCertificateInstalled(eapTlsConfig->eapTlsRootCertificate.id)) getRootCACertificate = true; if (EapTlsResult_Error == EapTls_ValidateCertificates(eapTlsConfig->eapTlsRootCertificate.id, NULL, NULL)) getClientCertificate = true; #if defined(WEBAPI_SERVER) // Call MDM WebApi "auth" method iRes = EapTls_CallWebApi(eapTlsConfig->mdmWebApiInterfaceUrl, NULL, "{}", eapTlsConfig->mdmWebApiRootCertificate.relativePath, responseBlock); #endif #if defined(WEBAPI_KESTREL) snprintf(query_str, sizeof(query_str) - 1, "%s=%s&%s=%s", webApi_RootCertificateField, getRootCACertificate ? strTrue : strFalse, webApi_ClientCertificateField, getClientCertificate ? strTrue : strFalse); iRes = EapTls_CallWebApi(eapTlsConfig->mdmWebApiInterfaceUrl, query_str, NULL, eapTlsConfig->mdmWebApiRootCertificate.relativePath, responseBlock); #endif } else { Log_Debug("ERROR: EAP-TLS configuration and/or response MemoryBlock are NULL!"); iRes = EapTlsResult_BadParameters; } #endif return iRes; } EapTlsResult EapTls_WebApiRegisterDevice(EapTlsConfig *eapTlsConfig) { EapTlsResult iRes = EapTlsResult_Error; if (NULL != eapTlsConfig) { #if defined(WEBAPI_SERVER) # if defined(MDM_NEEDS_REGISTRATION) MemoryBlock responseBlock = { .data = NULL, .size = 0 }; // Add further specific parameters here (currently set as an empty JSON body). iRes = EapTls_CallWebApi(g_webApiInterfaceRegisterUrl, NULL, "{}", eapTlsConfig->mdmWebApiRootCertificate.relativePath, &responseBlock); size_t nullTerminatedJsonSize = responseBlock.size + 1; char *nullTerminatedJsonString = (char *)malloc(nullTerminatedJsonSize); if (nullTerminatedJsonString == NULL) { Log_Debug("ERROR: could not allocate buffer for the WebAPI response.\n"); } else { // Copy the provided buffer to a null terminated buffer. memcpy(nullTerminatedJsonString, responseBlock.data, responseBlock.size); nullTerminatedJsonString[nullTerminatedJsonSize - 1] = 0; // Add the null terminator at the end. // Parse the response JSON_Value *rootProperties = json_parse_string(nullTerminatedJsonString); if (rootProperties == NULL) { iRes = EapTlsResult_Error; Log_Debug("ERROR parsing response.\n"); } else { JSON_Object *rootObject = json_value_get_object(rootProperties); // Add further specific returned JSON parsing here. } // Release the allocated memory. json_value_free(rootProperties); } // Release the allocated memory. free(nullTerminatedJsonString); free(responseBlock.data); # else iRes = EapTlsResult_Success; // No registretion required for the Kestrel sample WebApi. # endif #endif #if defined(WEBAPI_KESTREL) iRes = EapTlsResult_Success; // No registretion required for the Kestrel sample WebApi. #endif } else { Log_Debug("ERROR: EAP-TLS configuration and/or response MemoryBlock are NULL!"); iRes = EapTlsResult_BadParameters; } return iRes; }